package org.syntaxlisp.core.match.tree;

import org.syntaxlisp.core.InvalidException;
import lombok.Builder;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;

import java.util.HashSet;
import java.util.Set;

/**
 * @author HuangYijun
 * @data 2023-09-13 11:36
 **/
@Builder
@Data
public class NewMatchSeqNode {

    private static int ID_GENERATOR = 1;

    public enum SeqNodeType {
        DEF,
        BASE
    }

    private int id;

    private SeqNodeType type;

    private String def;

    private MatchState matchState;

    private int start;

    private int end;

    public static final int REDUCING = -1;

    /**
     * Node 为 Root 情况下 uppers 为空
     */
    private Set<NewMatchSeqNode> prevSet;

    /**
     * Node 为 Leaf 情况下 next 为空
     */
    private Set<NewMatchSeqNode> nextSet;

    /**
     *
     */
    private Set<NewMatchSeqNode> reducingNextSet;

    /**
     * type 为 DEF 情况下 subLastSeqNode 不为空
     */
    private NewMatchSeqNode subLastSeqNode;

    private Set<NewCurPvt> pvtRefSet;

    private Integer keyCode;

    public NewMatchSeqNode copy () {

        NewMatchSeqNode copyTarget = NewMatchSeqNode.builder()
                                                    .id(id)
                                                    .type(type)
                                                    .def(def)
                                                    .matchState(matchState)
                                                    .start(start)
                                                    .end(end)
                                                    .subLastSeqNode(subLastSeqNode)
                                                    .build();
        if (prevSet != null) {
            copyTarget.setPrevSet(new HashSet<>(prevSet));
        }
        if (nextSet != null) {
            copyTarget.setNextSet(new HashSet<>(nextSet));
        }
        if (reducingNextSet != null) {
            copyTarget.setReducingNextSet(new HashSet<>(reducingNextSet));
        }
        if (pvtRefSet != null) {
            copyTarget.setPvtRefSet(new HashSet<>(pvtRefSet));
        }
        return copyTarget;
    }

    public boolean canBeRemoved () {
        return CollectionUtils.isEmpty(nextSet)
                && CollectionUtils.isEmpty(reducingNextSet)
                && CollectionUtils.isEmpty(pvtRefSet);
    }

    public boolean addUpper (NewMatchSeqNode upper) {
        if (prevSet == null) {
            prevSet = new HashSet<>();
        }
        return addMatchSeqNode(upper, prevSet);
    }

    public boolean addNext (NewMatchSeqNode next) {
        if (nextSet == null) {
            nextSet = new HashSet<>();
        }
        return addMatchSeqNode(next, nextSet);
    }

    public boolean addReducingNetSet (NewMatchSeqNode reducingNext) {
        if (reducingNextSet == null) {
            reducingNextSet = new HashSet<>();
        }
        return addMatchSeqNode(reducingNext, reducingNextSet);
    }

    public void addPvtRef (NewCurPvt curPvt) {
        if (pvtRefSet == null) {
            pvtRefSet = new HashSet<>();
        }
        pvtRefSet.add(curPvt);
    }

    public boolean removePvtRef (NewCurPvt curPvt) {
        if (pvtRefSet == null) {
            throw new InvalidException("Can not remove removePvtRef cause pvtRefSet is null.");
        }
        return pvtRefSet.remove(curPvt);
    }

    /**
     *
     * @param target
     * @param targetSet
     * @return whether not contains target before add
     */
    private boolean addMatchSeqNode (NewMatchSeqNode target, Set<NewMatchSeqNode> targetSet) {
        return targetSet.add(target);
    }

    public boolean isRoot () {
        return CollectionUtils.isEmpty(prevSet);
    }

    public boolean isReducing () {
        if (SeqNodeType.DEF != type && REDUCING == end) {
            throw new InvalidException("Non-DEF-Seq-Node can not be reducing!");
        }
        return SeqNodeType.DEF == type && REDUCING == end;
    }

    public Integer getKey () {
        if (keyCode == null) {
            keyCode = (matchState.getSrc().getId() + "," + start + "," + end).hashCode();
        }
        return keyCode;
    }

    public boolean equals (Object otherNode) {
        if (!(otherNode instanceof NewMatchSeqNode)) {
            return false;
        }
        NewMatchSeqNode on = (NewMatchSeqNode) otherNode;
        return on.matchState.equals(matchState)
                && on.getStart() == start
                && on.getEnd() == end
                && on.getType() == type;
    }

    public String toString () {
        return matchState.toString() + "," + start + "," + end;
    }
}
